package goben

import (
	"net"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"strings"

	"gitee.com/zhangycsz/goben/util"
	"github.com/gorilla/sessions"
)

// 设置cookie的配置信息
type CookieSetting struct {
	Path     string // 限制二级目录访问，默认是全部
	Domain   string // 访问域名的地址
	MaxAge   int    //单位秒，0:不设置最大有效时间，<0过期,>0设置最大秒失效，默认:2小时
	Secure   bool   //标记为Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记，敏感信息也不应该通过Cookie传输，因为Cookie有其固有的不安全性
	HttpOnly bool   //为避免跨域脚本 (XSS) 攻击 设置true:客户端无法获取到，否则客户端可以获取
}

// 设置默认的session配置
type SessionSetting struct {
	Name     string        //session的名称，默认是GOSESSID
	MaxAge   int           //单位秒,默认3600小时
	Path     string        //限制二级目录访问，默认是全部
	Secure   bool          //标记为Secure 的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。但即便设置了 Secure 标记，敏感信息也不应该通过Cookie传输，因为Cookie有其固有的
	HttpOnly bool          //为避免跨域脚本 (XSS) 攻击 设置true:客户端无法获取到，否则客户端可以获取
	SameSite http.SameSite //1:默认，2:宽松模式，3:严格模式

	Domain string         //访问域名的地址
	Store  sessions.Store //设置cookie的存储方式，默认是内存存储
}

// 日志的设置方式
type LogSetting struct {
	LogName string //写入日志的文件名称
	LogPath string //写入日志的路径 默认是当前程序log_程序名称
	MaxDay  int64  //日志保留的天数 默认保留7天
}

// 设置头文件
type HeaderSetting struct {
	Origin          []string                 //默认的头信息 Access-Control-Allow-Origin:*(全部) ,指定地址:http://ip,多个需要用逗号隔开
	Methods         []string                 //默认的头信息 Access-Control-Allow-Methods:*(全部) ,指定方法:GET,POST,多个需要用逗号隔开
	Headers         []string                 //Content-Type
	AllowOriginFunc func(origin string) bool //自定义的头信息 Access-Control-Allow-Origin:*(全部) ,指定地址:http://ip,多个需要用逗号隔开
}

// 图形验证码
type CaptchaSetting struct {
	CaptchaName string //验证码的名称，默认是captchaId
	KeyLong     int64  //验证码的长度,默认是6位数字加字母组合
	MaxWidth    int    //验证码的最大宽度，默认100px
	MinWidth    int    //验证码的最小宽度，默认50px
	Ext         string //验证码的类型，默认是.png 或者.wav
}

// 上传文件
type UploadSetting struct {
	TmpPath string //临时文件的目录，默认是当前程序的目录/tmp
}

// 设置默认的统一配置
type DefaultSetting struct {
	Name       string          //程序名称
	Path       string          //程序运行的目录，默认是当前程序的目录
	HeaderSet  *HeaderSetting  //默认的头信息
	CookieSet  *CookieSetting  //cookie的设置信息
	SessionSet *SessionSetting //session的设置信息
	LogSet     *LogSetting     //日志的设置信息
	Captcha    *CaptchaSetting //验证码的设置信息
	Upload     *UploadSetting  //上传文件的设置信息
}

// 默认的程序目录,返回程序目录和程序名称
func getPramInfo() (dirPath string, name string) {
	dirPath = "./"
	name = "test"
	_path := ""
	if len(os.Args) > 0 {
		_path = os.Args[0]
	}
	if _path != "" {
		if strings.Contains(_path, "go-build") { //说明是测试环境
			return
		}
		dirPath = filepath.Dir(_path) + "/"
		name = filepath.Base(_path)
		return
	}
	ex, err := os.Executable()
	if err != nil {
		return
	}
	if strings.Contains(_path, "go-build") {
		//说明是测试环境
		return
	}
	dirPath = filepath.Dir(ex) + "/"
	name = filepath.Base(ex)
	return
}

// 设置默认的统一配置
func NewDefaultSetting() *DefaultSetting {
	path, name := getPramInfo()
	d := &DefaultSetting{
		Name: name,
		Path: path,
	}
	return d
}

func getNetworkIp() string {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "127.0.0.1"
	}
	for _, addr := range addrs {
		if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				return ipnet.IP.String()
			}
		}
	}
	return "127.0.0.1"
}

// 启动默认的配置
func NewGoBenStart() {
	path := gdefaultSetting.Path
	name := gdefaultSetting.Name
	sessionkey := name + "_secret"
	if gdefaultSetting.HeaderSet == nil {
		//gdefaultSetting.HeaderSet = &HeaderSetting{Origin: []string{"*"}, Methods: []string{"GET", "POST"}, Headers: []string{"Origin", "Content-Type"}, AllowOriginFunc: func(origin string) bool { return true }}

	}
	if gdefaultSetting.CookieSet == nil {
		//gdefaultSetting.CookieSet = &CookieSetting{Path: "/", MaxAge: 60 * 60 * 2}
	}

	if gdefaultSetting.SessionSet == nil {
		//store := cookie.NewStore([]byte(sessionkey))
		session_name := util.GetMd5(getNetworkIp()) + "_GOSESSID"
		store := sessions.NewCookieStore([]byte(sessionkey))
		store.Options = &sessions.Options{
			Path:     "/",
			Domain:   "",
			MaxAge:   60 * 60,
			Secure:   false,
			SameSite: 2,
			HttpOnly: true,
		}
		//store.Options.HttpOnly = true
		gdefaultSetting.SessionSet = &SessionSetting{Name: session_name, Store: store}

	}

	gdefaultSetting.LogSet = &LogSetting{MaxDay: 7, LogName: "log_", LogPath: path + "/log"}
	if gdefaultSetting.Captcha == nil {
		gdefaultSetting.Captcha = &CaptchaSetting{CaptchaName: "captchaId", KeyLong: 4, Ext: ".png"}
	}
	if gdefaultSetting.Upload == nil {
		gdefaultSetting.Upload = &UploadSetting{TmpPath: path + "tmp"}
	}

}

// 设置默认的统一配置
var gdefaultSetting *DefaultSetting = NewDefaultSetting()

// 设置默认的
func SetDefaultSetting() *DefaultSetting {
	return gdefaultSetting
}

// 需要设置默认的中间件
func DefaultSessionUse() HandlerFunc {
	return func(c *Context) {
		store := gdefaultSetting.SessionSet.Store
		name := gdefaultSetting.SessionSet.Name
		session, err := store.Get(c.Request, name)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		// 将会话存入Gin上下文
		c.Set(name, session)
		// 处理请求
		c.Next()

		// 保存会话
		if err := session.Save(c.Request, c.Writer); err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
	}
}

// 设置默认的头信息
func SetDefaultSettingUser() HandlerFunc {
	return func(c *Context) {
		if len(gdefaultSetting.HeaderSet.Headers) > 0 {
			c.Writer.Header().Set("Access-Control-Allow-Origin", strings.Join(gdefaultSetting.HeaderSet.Headers, ","))
		} else {
			c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		}
		c.Next()
	}
}

// 结构图方法，如果是get_,post_put_delete_等前缀的方法，会自动识别为对应的http方法
// 自动添加结构体，自动检查mvc的路径,用&controller{}
func AutoRegister(r *Engine, controller interface{}) (err error) {

	ctrlType := reflect.TypeOf(controller)
	ctrlValue := reflect.ValueOf(controller)
	if ctrlType.Kind() != reflect.Ptr {
		return nil
	}
	controllerName := strings.ToLower(ctrlType.Elem().Name())
	modelName := "" //模块的名称
	pathName := reflect.TypeOf(controller).String()
	if pathName != "" {
		pathName = strings.Replace(pathName, "*", "", -1)
		arr := strings.Split(pathName, ".")
		if len(arr) > 0 {
			modelName = arr[0]
		}
	}
	if modelName != "" {
		if modelName == "app" {
			modelName = ""
		}
	}
	if modelName != "" {
		modelName = "/" + modelName
	}

	for i := 0; i < ctrlType.NumMethod(); i++ { //遍历结构体中的方法

		method := ctrlType.Method(i)
		methodValue := ctrlValue.Method(i)
		actionName := method.Name
		_method := ""
		if actionName != "" {
			actionName = strings.ToLower(actionName)
			arr := strings.Split(actionName, "_")
			if len(arr) > 0 {
				if arr[0] == "get" || arr[0] == "post" || arr[0] == "put" || arr[0] == "delete" {
					_method = arr[0]
					actionName = arr[1]
				}
			}
		}
		urlPath := modelName + "/" + controllerName + "/" + actionName

		if method.Type.NumIn() == 2 && method.Type.In(1) == reflect.TypeOf(&Context{}) {
			hanler := createGinHandler(methodValue)
			switch _method {
			case "get":
				r.GET(urlPath, hanler)
			case "post":
				r.POST(urlPath, hanler)
			case "put":
				r.PUT(urlPath, hanler)
			case "delete":
				r.DELETE(urlPath, hanler)
			default:
				r.GET(urlPath, hanler)
				r.POST(urlPath, hanler)
			}
		}

	}

	return nil
}

func createGinHandler(method reflect.Value) HandlerFunc {
	return func(c *Context) {
		method.Call([]reflect.Value{reflect.ValueOf(c)})
	}
}
